import { IMeshData } from '@app-nextmesh/types';
import { isEmpty } from 'lodash';
import {
  BufferAttribute,
  BufferGeometry,
  DoubleSide,
  GLSL3,
  InstancedBufferAttribute,
  InstancedBufferGeometry,
  MeshBasicMaterial,
  ShaderMaterial,
  Uint32BufferAttribute,
  Vector3Tuple,
} from 'three-legacy';
import { BufferGeometryUtils } from 'three-legacy/examples/jsm/utils/BufferGeometryUtils';
import { GlobalLineMaterial } from './materials';
import { MyLineSegments } from './myLineSegments';
import { MyMesh } from './myMesh';
import { MyPoints } from './myPoints';

const fragmentShader = /* glsl */ `
layout(location = 0) out int out_id;
flat varying int vid;

void main() {
  out_id = vid; // 将传递的 ID 赋值给片元
}
`;

const pointPickingMaterial = new ShaderMaterial({
  glslVersion: GLSL3,
  vertexShader: /* glsl */ `
  attribute int id;
  attribute vec3 translation; // 每个实例的偏移位置
  flat varying int vid;

  void main() {
    vid = id; // 将 ID 传递给片元着色器
    vec3 transformed = position + translation; // 计算每个实例的世界位置
    gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
  }
`,
  fragmentShader,
});

/**
 * 生成0维网格模型的mesh，可拾取，但不可见；所以只用于拾取
 * @param modelDxid 模型dxid
 * @param points 点二维数组
 * @param d0 key是id，value是点的index
 * @returns 生成的拾取mesh
 */
function makeD0Mesh(modelDxid: string, points: Vector3Tuple[], d0: object) {
  const ids: number[] = [];
  const positions: number[] = [];
  const keys = Object.keys(d0);
  keys.forEach((k) => {
    ids.push(parseInt(k));
    positions.push(...points[d0[k]]);
  });
  const geometry = new InstancedBufferGeometry();
  geometry.instanceCount = keys.length;
  const positionAttr = new BufferAttribute(new Float32Array([0, 0, 0]), 3);
  const translationAttr = new InstancedBufferAttribute(positions, 3);
  const idAttr = new InstancedBufferAttribute(ids, 1);
  geometry.setAttribute('position', positionAttr);
  geometry.setAttribute('translation', translationAttr);
  geometry.setAttribute('id', idAttr);

  const d0PickingMesh = new MyPoints(geometry, pointPickingMaterial, modelDxid);
  return { d0PickingMesh };
}

const GlobalPickingMaterial = new ShaderMaterial({
  glslVersion: GLSL3,
  vertexShader: /* glsl */ `
    attribute int id;
    flat varying int vid;

    void main() {
      vid = id; // 将 ID 传递给片元着色器
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader,
});

/**
 * 生成1维网格模型的mesh，可见且可拾取
 * @param modelDxid 模型dxid
 * @param points 点二维数组
 * @param d1 key是id，value是点的index数组
 * @returns 生成的可见和拾取mesh
 */
function makeD1Mesh(modelDxid: string, points: number[][], d1: object) {
  const geometries: BufferGeometry[] = [];
  const keys = Object.keys(d1);
  keys.forEach((k) => {
    const geometry = new BufferGeometry();
    const positionAttr = new BufferAttribute(
      new Float32Array(d1[k].flatMap((v) => points[v])), // 每条线都是两个点，如果大于2个点的话，需要将中间的点坐标重复一次
      3,
    );
    geometry.setAttribute('position', positionAttr);
    const idArray = new Uint32Array(positionAttr.count); // TODO Uint32是否要和Shader的类型一致
    idArray.fill(parseInt(k));
    const idBufferAttr = new Uint32BufferAttribute(idArray, 1);
    geometry.setAttribute('id', idBufferAttr);
    geometries.push(geometry);
  });
  const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);

  const d1Mesh = new MyLineSegments(
    mergedGeometry,
    GlobalLineMaterial,
    modelDxid,
  );
  const d1PickingMesh = new MyLineSegments(
    mergedGeometry,
    GlobalPickingMaterial,
    modelDxid,
  );
  return { d1Mesh, d1PickingMesh };
}

function makePositionAndIndex(points: number[][], facets: number[][]) {
  const oldIndices: number[] = []; // 旧的index数组，索引就是新的index
  const positions: number[] = [];
  const indices: number[] = [];

  facets.forEach((v) => {
    v.forEach((v) => {
      let newIndex = oldIndices.indexOf(v);
      if (newIndex === -1) {
        newIndex = oldIndices.length;
        oldIndices.push(v);
        positions.push(...points[v]); // 没找到则添加新的点坐标
      }
      indices.push(newIndex);
    });
  });
  return { positions, indices };
}

/**
 * 生成3维网格模型的mesh
 * @param points 全部的点二维数组
 * @param facets 全部的面片二维数组
 * @param facetIndices 网格单元的面片索引数组
 * @param id 网格单元id
 * @returns 生成的mesh
 */
function makeGeometry(
  points: number[][],
  facets: number[][],
  facetIndices: number[],
  id: string,
) {
  const geometry = new BufferGeometry();
  const { positions, indices } = makePositionAndIndex(
    points,
    facetIndices.map((v) => facets[v]),
  );
  const positionAttr = new BufferAttribute(new Float32Array(positions), 3);
  // TODO 可以根据indices的最大值，分配合适的内存
  const indexAttr = new BufferAttribute(new Uint32Array(indices), 1);
  geometry.setAttribute('position', positionAttr);
  geometry.setIndex(indexAttr);
  // TODO 可以根据id大小，分配合适的内存
  const idArray = new Uint32Array(positionAttr.count);
  idArray.fill(parseInt(id));
  const idBufferAttr = new Uint32BufferAttribute(idArray, 1);
  geometry.setAttribute('id', idBufferAttr);
  return geometry;
}

/**
 * 生成2维、3维网格模型的mesh
 * @param modelDxid 模型dxid
 * @param points 点二维数组
 * @param facets 面片的二维数组，每个面片是点的index数组
 * @param d2 key是id，value是面片的index数组
 * @param d3 key是id，value是面片的index数组
 * @returns 生成的可见和拾取mesh
 */
function makeD2D3Mesh(
  modelDxid: string,
  points: Vector3Tuple[],
  facets: Vector3Tuple[],
  d2: object,
  d3: object,
) {
  const geometries: BufferGeometry[] = [];
  // 网格单元的线框，记录线框点的索引
  const wirePointIndices = [];
  if (!isEmpty(d2)) {
    const keys = Object.keys(d2);
    keys.forEach((k) => {
      const v = d2[k];
      // v = [0]三角网格单元 或 [1,2]四边形网格单元等等
      //
      if (v.length === 1) {
        // 三角网格单元，线框需要首尾相连
        const v2 = facets[v[0]]; // v3= [0, 1, 2]
        wirePointIndices.push(v2[0], v2[1], v2[1], v2[2], v2[2], v2[0]);
      } else {
        // 其他的中间索引重复一次即可
        v.forEach((v1) => {
          const v2 = facets[v1];
          wirePointIndices.push(v2[0], v2[1], v2[1], v2[2]);
        });
      }

      const geometry = makeGeometry(points, facets, d2[k], k);
      geometries.push(geometry);
    });
  }
  if (!isEmpty(d3)) {
    const keys = Object.keys(d3);
    keys.forEach((k) => {
      d3[k].forEach((v: number[]) => {
        // v = [0, 1, 2, 4] 四面体网格单元 或 = [0, 1, [2, 3], [4, 5], [6, 7]] 三棱柱网格单元
        if (Array.isArray(v)) {
          // [2, 3] => [[0, 1, 2], [2, 3, 0]] => => [0, 1, 1, 2, 2, 3, 3, 0]
          v.forEach((v2) => {
            wirePointIndices.push(v2[0], v2[1], v2[1], v2[2]);
          });
        } else {
          // 0 => [0, 1, 2] => [0, 1, 1, 2, 2, 0]
          const v2 = facets[v]; // v3= [0, 1, 2]
          wirePointIndices.push(v2[0], v2[1], v2[1], v2[2], v2[2], v2[0]);
        }
      });

      const geometry = makeGeometry(points, facets, d3[k], k);
      geometries.push(geometry);
    });
  }

  const wireGeometry = new BufferGeometry();
  const positionAttr = new BufferAttribute(
    new Float32Array(wirePointIndices.flatMap((v) => points[v])), // 每条线都是两个点，如果大于2个点的话，需要将中间的点坐标重复一次
    3,
  );
  wireGeometry.setAttribute('position', positionAttr);
  const wireMesh = new MyLineSegments(
    wireGeometry,
    GlobalLineMaterial,
    modelDxid,
  );

  const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
  const meshMaterial = new MeshBasicMaterial({
    vertexColors: false,
    color: 0xff8c00,
    side: DoubleSide,
    flatShading: true,
    polygonOffset: true,
    polygonOffsetFactor: 0.75,
    polygonOffsetUnits: 0.75,
  });
  const d2d3Mesh = new MyMesh(mergedGeometry, meshMaterial, modelDxid);
  d2d3Mesh.add(wireMesh);

  const d2d3PickingMesh = new MyMesh(
    mergedGeometry,
    GlobalPickingMaterial,
    modelDxid,
  );
  return { d2d3Mesh, d2d3PickingMesh };
}

/**
 * 生成网格模型的mesh
 * @param data 网格数据 IMeshData
 * @returns 生成的可见和拾取mesh
 */
export function makeMesh(meshData: IMeshData) {
  const { id, points, facets, d0, d1, d2, d3 } = meshData;
  const meshes: {
    d0PickingMesh?: MyPoints;
    d1Mesh?: MyLineSegments;
    d1PickingMesh?: MyLineSegments;
    wireMesh?: MyLineSegments;
    d2d3Mesh?: MyMesh;
    d2d3PickingMesh?: MyMesh;
  } = {};
  if (!isEmpty(d0)) {
    meshes.d0PickingMesh = makeD0Mesh(id, points, d0).d0PickingMesh;
  }

  if (!isEmpty(d1)) {
    const { d1Mesh, d1PickingMesh } = makeD1Mesh(id, points, d1);
    meshes.d1Mesh = d1Mesh;
    meshes.d1PickingMesh = d1PickingMesh;
  }
  if (!isEmpty(d2) || !isEmpty(d3)) {
    const { d2d3Mesh, d2d3PickingMesh } = makeD2D3Mesh(
      id,
      points,
      facets,
      d2 ?? {},
      d3 ?? {},
    );
    meshes.d2d3Mesh = d2d3Mesh;
    meshes.d2d3PickingMesh = d2d3PickingMesh;
  }

  return meshes;
}
